整篇會分成以下幾個部分:
every
這個 method 的全寫應該是 Array.prototype.every
,有興趣可以看 Day 2 的介紹,這邊會直接使用every()
作為替代。
Array method 有不少會使用到 callback function,如果尚不熟悉的話,可以看 Day 2 的介紹。
範例使用的 callback 都會使用箭頭函式做介紹,如果尚不熟悉的話可以參考 MDN 的介紹。
最後會透過分析 ECMAScript 來驗證是否有吻合,如果覺得 ECMAScript 有點艱澀難懂,我們在 Day 4 、Day 5 有介紹其相關術語可以幫助閱讀。
當想要確認陣列的全部元素是否有符合自訂的條件時。
如果每個元素都通過測試就回傳 true,否則回傳 false
(即使只有一個沒通過)。
every((element, index, array) => {
/* 測試條件 */
})
every(callbackFn,thisArg)
every(function(element, index, array) {
/* 測試條件 */
}, thisArg)
every()
的第 1 個參數為 callback, 第 2 個參數為選擇性 (optional) 的 thisArg
。
callback
這個 callback 又稱為 testing function 或 predicate ,顧名思義它會被拿來測試某些條件,再準確一點應該稱作斷言 (assert) ,因為它最後回傳的值會被強制轉換成布林值 (true 或 false)!
當這個 callback 被呼叫時會帶入 element
、index
、array
三個參數。
every()
會按照陣列元素的順序依次 (升冪) 呼叫這個 callback,直到這個 callback 回傳 true 或當陣列元素已被遍歷完畢即停止,換句話說,如果這個陣列有 5 個元素,那這個 callback 至多會被呼叫 5 次。
element
陣列當前的元素 (element),callback 的第 1 個參數,為 every()
當前遍歷到的元素,也就表示 element
會依陣列的順序動態變化。
index
陣列當前元素的索引值 (index),callback 的第 2 個參數,為 every()
當前遍歷到的元素其索引值,也就表示 index
會依陣列的順序動態變化。
array
呼叫 every()
的陣列本身 (被遍歷的陣列本身), callback 的第 3 個參數,不論 every()
當下遍歷到哪個元素上, array
都會指向被遍歷的陣列本身,也就是呼叫 every()
的陣列本身。
thisArg
為 every()
的第 2 個選擇性參數,它會被傳入 callback 並作為其 this
的值,否則就會是 undefined
。
請注意,如果 callback 使用箭頭函式的話則沒有作用!
可以參考範例的 Example - 4 寫法。
當陣列所有的元素都通過測試 (所有 callback
皆回傳 true),every()
即回傳 true,否則即回傳 false (即使只有一個 callback
回傳 false)。
不會變動到原陣列。
const names = ['Pedro', 'Angela', 'Dirk', 'Hanna']
const result = names.every(name => name.length > 3)
console.log(result)
// true
names
names
呼叫 every()
時會順著原型鏈拿到放在 Array.prototype
指向的 prototype 物件上的 every()
methodevery()
呼叫時需傳入了一個 callback (testing function),因此我們直接在 every()
的參數裡定義一個帶有 method
參數的箭頭函式every()
被呼叫時, every()
會執行 callback,並帶入 element
、index
、array
等 3 個參數,其中的 element
會被指派給 name
這個參數every()
便回傳 true
result
index
參數const names = ['Pedro', 'Angela', 'Dirk', 'Hanna']
const result = names.every((name, index) => {
if (index === 2) return name === 'Dirk'
return typeof name === 'string'
})
console.log(result)
// true
index
在第 3 個元素做額外的判斷includes()
檢查是否為子集合 (取自 MDN)const isSubset = (array1, array2) =>
array2.every(element => array1.includes(element))
const array = [1, 2, 3, 4, 5, 6, 7]
const subArray1 = [5, 7, 6]
const subArray2 = [5, 8, 7]
console.log(isSubset(array, subArray1))
// true
console.log(isSubset(array, subArray2))
// false
array2
呼叫 every()
時,其全部元素便會被遍歷一次arrary1
呼叫 includes()
來檢查陣列是否包含此元素thisArg
參數const prices = [15, 10, 20, 5]
const quantities = [8, 12, 7, 30]
const result = prices.every(function(price, index) {
return (price * this[index]) > 100
}, quantities)
console.log(result)
// true
quantities
被作為 every()
的第 2 個參數傳入this
會指向 quantities
如果呼叫 every()
的陣列是一個 稀疏陣列 (sparse array),其中的空值元素 (empty slot) 不會被遍歷,也就表示 callback
會跳過對這個元素的判斷,因此可能會出現一些非預期的情況。
請注意 callback 定義時的參數順序,依序應為 element
、index
、array
,但不可隨意略過,就算你只想使用 index
而不需要 element
, 可參考範例的 Example 2 寫法。
參數名稱可以隨意命名但通常會有一些慣例以增加可讀性,例如上面的範例便使用 name
來表示 names
這個陣列的每個元素。
有一點值得注意的是,雖然 every()
不會變動到原陣列,但我們傳進去的 callback 卻有可能 ,而陣列元素被遍歷的範圍在第一次呼叫 callback 前就已經確立好了 (也就是 every()
被呼叫後但 callback 尚未被呼叫),因此有可能會發生以下的狀況:
undefined
上述這種高併發 (concurrent) 的更動會導致程式碼非常難以閱讀,非常不建議使用 (除非有特殊的情境)。
Array.prototype.every(callbackfn)
this
轉型成一個 object 後指派給 O
O
的長度並指派給 len
callbackfn
是不可呼叫的 object 則丟出一個 TypeErrork
k
< len
時,重複以下步驟k
轉型成 Number 再轉型成 String 後指派給 Pk
O
是否有 Pk
這個屬性,並將結果 (布林值) 指派給 kPresent
kPresent
為 true,則進行以下步驟O
的 Pk
屬性的值,並指派給 kValue
callbackfn
並帶入 « kValue, ?(k), O »
這個 argumentList,並將 callbackfn
回傳的值指派給 testResult
testResult
為 false,則回傳 falsek
= k
+ 1ECMAScript 其實並沒有規定使用 every()
的物件必須是一個陣列,從步驟 1 跟 Note 2 可以看得出來。
演算法的前 3 個步驟都是用來做一些前置處理,包括轉型、確認長度、確認參數是否為一個 function 等...。
步驟 5 便開始執行迴圈,最大圈數為陣列長度,計數從 0 開始,重複以下步驟:
- 依照當前計數的值取出對應的陣列元素
- 呼叫 callbackfn
, 並帶入被取出的 元素、 當前計數、陣列本身 這 3 個參數
- 如果 callbackfn
的測試結果為 false,便終止 every()
並回傳 false
- 如果為 true 則繼續直至迴圈結束
如果全部測試通過,則終止 every()
並回傳 true。
整個演算法步驟出現了 5 次 ?
, 代表有 5 處的 abstract operation 有機會丟出錯誤,例如步驟 2 的 LengthOfArrayLike(O)
,當傳進去的是一個 Symbol 或 BingInt,裡面的 ToNmber()
便會丟出一個 TypeError。
如果出現 !
,則代表這個 abstract operation 絕對不會丟出錯誤,例如步驟 5 -> 步驟a 的 ToString()
它會在參數是一個 Symbol 時丟出一個 TypeError,但我們確定丟進去的是一個 Number (F(k)
),因此不會有丟出錯誤的可能。
every()
可能是相對較冷門的 method,也可以被很多方法取代,那為什麼要使用它?調用我們 Day 1 提到的 - 乾淨、直覺、可讀性高!
最後,希望大家可以開心地使用各種咩色,體驗它帶給你的便利,祝大家歸剛沒煩惱。
Array.prototype.every - MDN
Draft ECMA 262 - TC39